Skip to content

Bowden path calibration - reworked flow, encoder-based distance, new-design theming - #168

Merged
decay71 merged 29 commits into
decay71:mainfrom
physicsG:feat/bowden-path-calibration
Oct 28, 2026
Merged

decay71 merged 29 commits into
decay71:mainfrom
physicsG:feat/bowden-path-calibration

Conversation

@physicsG

@physicsG physicsG commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Work in progress. This runs on my U1 (one ACE 2 Pro, head mode), but it still needs more testing before it should be merged. See "Testing" at the bottom. Opening it as a draft so you can look at the direction.

It builds on my previous bowden-path calibration work that was ported into main, is merged up to 1.27b, and does three things: it answers the feedback on the first version, it fixes a few problems that turned up while doing so, and it aligns the calibration tab with the new design.

The feedback this started from

The length measurement worked well. These were the issues, each with what this PR does about it:

Issue Resolution
Splitter retract only goes backwards; an overshoot means starting over. The forward steps are already in main. Added: a hold-to-pull-back button for the long stretch, labelled steps in both directions, a Stop that keeps the distance covered, and undo for the hub mark.
Verification needs manual jogging to the toolhead in small steps. The verification feed carries on by itself, up to 200 mm past the saved distance, until the toolhead sensor trips. Manual steps are only the fallback after that.
The jogged distance is not added to the saved value. A sensor position more than 10 mm from the saved distance replaces it for the rest of the round trip and in the values offered for saving.
The second verification round did not reach the toolhead and aborted. Two causes, two fixes. The search above covers a shortfall. And on ACE 2 Pro the distance now comes from the lane encoder, which removes the over-retract behind the shortfall (see below). On ACE Pro, without an encoder, the over-retract remains and only the search helps.
Re-measurement is not automatic. It is now: same mechanism as the two rows above. The result panel then says "Verification changed the values".
Measuring one slot still requires checking the others. Not sure I understood this one. Setup now only picks ACE, slot and toolhead, a measurement starts with one click, the two confirmation steps are gone and reinserting the filament is a hint. I did not find a step that makes you check other slots, so tell me where it happens if this is not it.
The flow assumes centimetre accuracy that ACE transport does not deliver. Handled with margins rather than with more precision: see the next two rows. The full retract is the one value saved exactly as measured.
Load length has no margin on top of the measured value. Default margin is 150 mm (was 20). The feed stops at the toolhead sensor anyway.
Swap retract risks stopping short of the splitter. Default margin is 100 mm (was 20), capped below the full retract with a note when that happens. At the hub stop during verification you can pull back further, and that distance is added to the saved value.

What else turned up

  • The measured distance was rounded up to the step size. Position was commanded speed x elapsed time, counted from when the command was queued. The ACE starts late, so the estimate ran ahead and was capped at the chunk length: results landed on the 100 mm grid, and the return then over-retracted by the same amount (my verification needed 50 to 60 mm more than the measurement). On V2 the lane encoder is now the position source in the feed direction, with one more reading after the lane has stopped. V1 keeps the time estimate.
  • The encoder restarts with every motion command, and a reading requested right behind the command can still hold the previous move's value. That inflated the return span (1976 counts for 1400 mm in my log). Those readings are now ignored, and each move logs commanded vs encoder distance.
  • A stopped, cancelled or failed run left the filament in the tube with no way back, and the UI then allowed a new measurement from there. New: ACE_CALIBRATION_STOP / RESUME / HOME, a recovery bar in the UI ("Carry on" / "Return filament to the ACE"), and a new run or an ACE_LOAD_HEAD is refused while calibration filament is still in the tube.
  • ACE_UNLOAD_HEAD CALIBRATION_PREPARE=1 was not wired up in main. The web UI sends it and the helpers exist, but cmd_ACE_UNLOAD_HEAD never read it, so the preparation unload could not be cancelled. Restored, together with the cancel checkpoints in _run_tipform and the abort on print start and Klippy disconnect.
  • A standstill-guard failure could be overwritten by the state that followed it.
  • After saving, "verify saved values" ran on the values Klipper still had loaded. Verification is now blocked until the printer has restarted, and the result panel has a restart button.
  • The measuring feed limit hung on the configured load_length (+300 mm), the very value being measured. It is now +1000 mm.
  • Verification no longer stops at the toolhead (the sensor has already confirmed it). It stops once, at the hub, where you can answer "not clear, pull back more"; that distance is added to the hub clearance.

Theme alignment

The calibration tab was painted with style.css's fixed dark colours and rounded cards in every design. style-new.css now has a calibration section under html[data-theme="new"]:

  • square, line-drawn surfaces and the heading sizes of the Spools and Config tabs;
  • colours only through the design tokens, so the light shade needs no rules of its own;
  • outlined buttons, accent fill for the one next step;
  • a phone-width layout (vertical route, table that fits) for the classic and the new design.

The classic design is unchanged apart from the phone-width rules. All calibration strings are in the en and de catalogs; zh has no calibration section and falls back to English.

Open questions

  • When to switch CALIBRATION_TAB? It is set to true now.
  • Vocabulary in the UI is now: park position, hub, toolhead distance, hub clearance, margin (was preload reference / splitter / anchor / allowance).

Testing

Run on one U1 with one ACE 2 Pro in head mode. Not tested yet:

  • ACE Pro (V1, no encoder) on hardware
  • multi mode and more than one ACE

Screenshots

From the web UI on the printer. The setup and "check this route" screens are live; the mid-run screens were captured with a test state injected into the page, no filament was moved for them.

Setup: only the route
Setup

Measure, step 2: clear the hub
Clear the hub

Verify: ready
Verify ready

Verify: the one stop, at the hub, with "pull back more"
Verify hub stop

Results: saved, measured, margin, will save
Results

Stopped mid-run, light shade of the new design
Stopped, light shade

physicsG added 15 commits August 7, 2026 14:11
Upstream absorbed the bowden-path calibration feature in cf4e538 and
extended it (FEED_JOG, i18n, generic sensor discovery), so main's
versions are taken for every conflicted file.

Re-applied on top of main's ace.py, since upstream's copy lacks them
while its web UI still sends them:
- ACE_UNLOAD_HEAD CALIBRATION_PREPARE=1 ACE= SLOT= handling (route
  validation, unknown-source recovery, cancellable preparation unload)
- cancel checkpoints in _run_tipform
- calibration abort on print start and klippy disconnect
Upstream ships the tab hidden behind CALIBRATION_TAB = false.
…gins

Verification no longer stops dead at the saved toolhead distance. It runs
one long move to 150 mm short of it, then 50 mm steps up to 200 mm past
it until the toolhead sensor trips. A sensor position more than 10 mm
from the saved distance replaces it for the rest of the round trip and
for the proposed config values; manual jogging remains only as the
fallback after the automatic search.

Default margins on the saved values go from 20/20 mm to 150 mm (load)
and 100 mm (swap retract), the swap retract is capped below the full
retract, and a session primed from saved values no longer adds the swap
margin a second time on apply.
The measuring feed and the verification feed booked distance as commanded
speed x elapsed time, counted from when the command was queued. The ACE
starts late, so the estimate ran ahead and was capped at the chunk length:
results landed on the 100 mm (measure) / 50 mm (verify) grid, rounded up,
and the return then over-retracted by the same amount.

On V2 the per-command lane encoder is now the position source in the feed
direction, with one more reading after the lane has stopped at the sensor.
A reading requested right behind a motion command can still hold the
previous command's value; those are ignored, which also fixes the inflated
return encoder span. With a working encoder the verification feed is one
continuous move again. V1 keeps the time estimate.

Also: a standstill-guard failure is no longer overwritten by the state
that follows it, reset is refused during a forward jog, each move logs
commanded vs encoder distance, and the UI marks a time-estimated tip as
such and holds it back instead of showing the splitter as passed early.
Printer side
- ACE_CALIBRATION_STOP halts any calibration move and books the distance
  it covered; ACE_CALIBRATION_RESUME carries on a stopped feed, return or
  verification.
- ACE_CALIBRATION_HOME pulls the filament back to the ACE from wherever a
  stopped, cancelled or failed run left it. A new run is refused while
  calibration filament is still in the tube, and so is ACE_LOAD_HEAD.
- RETRACT accepts long pulls (hold-to-pull-back), MARK=undo removes the
  hub mark, the return is one move.
- Verification no longer stops at the toolhead; at the hub stop the user
  can pull back further (ACTION=hub_jog) and that distance is added to
  the hub clearance.
- The measuring feed is one move once the lane encoder has reported, and
  its limit no longer hangs on the configured load length (+1000 mm).

Web UI
- Setup only picks the route; where the result is saved is chosen in
  Results, next to the save button.
- One click starts a measurement; reinserting the filament is a hint.
- Hub step: hold-to-pull-back, labelled steps in both directions, one
  button that marks and returns, undo during the return.
- A recovery bar offers carry on / return filament to the ACE whenever a
  run is stopped.
- Results: one table (saved, measured, margin, will save), a note when
  the hub clearance is capped or slots keep their own values, a restart
  button after saving, and no save button when nothing changed.
- Route diagram drawn to scale, one vocabulary, all strings in the en/de
  catalogs.
The calibration tab was painted with style.css's fixed dark colours and
rounded cards in every design. style-new.css now gives it the square,
line-drawn look of the other tabs under html[data-theme="new"], using
only the design tokens, so the light shade follows without rules of its
own. The classic design is unchanged.

Also: while the hub position has not been measured yet, the diagram
treats the two tubes as the two halves of the route and marks the hub as
not measured, instead of drawing the tip before a hub it may have passed.
Below 560 px the route runs top to bottom; the new design's 2 px tube
rule had collapsed the tubes there. Tubes get a fixed height again with
their lengths beside them, the step chips stay in one row, and the
results table drops the "saved now" column and the sub-lines so it fits
without scrolling sideways. Applies to the classic and the new design.
@decay71

decay71 commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Direction looks right, thanks. I have pulled the branch onto my dev tree for hardware tests (ACE Pro, multi mode, stop/resume/home); results in a few days. The missing CALIBRATION_PREPARE handling is a real gap on my side, good catch. Tab on is fine. Ping me when it is out of draft.

@physicsG physicsG added the enhancement New feature or request label Oct 13, 2026
@decay71
decay71 marked this pull request as ready for review October 17, 2026 10:24
@decay71
decay71 self-requested a review as a code owner October 17, 2026 10:24

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the encoder rework does what it should, the measurement on the ACE 2 is spot on. Here is what came up on the rest of our rig (2x ACE 2 Pro, 2x ACE Pro, head mode, build 256eaa1b):

  1. ACE 2: measurement works. Encoder distance 1749 mm, verify found the toolhead sensor at exactly 1749, 100 mm retract steps count 97 to 98. The 100 mm grid problem is gone.
  2. ACE Pro: the hold button unloaded the spool. Pressing it sent unwind_filament len=2297, the release 0.7 s later sent stop_feed_filament (answered success), but the ACE Pro kept unwinding the full length: on V1 that command stops a feed, not an unwind. The calibration assumed ~21 mm, so "Return filament to the ACE" unwound another 2276 mm and the filament left the unit (gate went empty). Please stop unwinds on V1 with stop_unwind_filament (not used anywhere in multiACE yet, so verify it on hardware), or drop the hold button on V1 and keep the bounded steps.
  3. After that the session was stuck. Starting the next calibration (ACE 2) ended in splitter checkpoint must be before the ACE preload reference: the old session still held the inflated retract count. A failed or abandoned run should reset cleanly. Also, the web showed the raw Moonraker JSON with the traceback instead of just the message.
  4. The "Coarse" row is not understandable. Nothing says it is a hold button until it is pressed; it reads like a step size next to the 5/10/25/100 buttons. Please label it as what it is. Or make it a start / stop, i can't press my mouse and check the printers cabling, they are not next to each other. Suggestion: one line for the path from the ACE to the head with a handle at the current tip position. Drag it left or right to the target and on release move exactly that difference as one bounded command. Easier to read, and it also removes the unbounded V1 unwind from point 2.
  5. Verify checks the raw measurement, not what gets saved. It feeds load_length and retracts swap_retract_length without the margins. It should run the values that will be written (load +150, swap retract +100), the swap retract is the one used on every swap.
  6. Saving forces a restart flow that does not fit. After saving you confirm a restart twice, but no restart happens, and until one does you cannot get back into the calibration. A restart is not needed right away anyway. Please just save, show a note that the new values apply to loads and swaps after the next restart, and let the user go on with the next ACE or slot. Then everything gets calibrated and the printer restarts once at the end.

…ed stop as the full move

On V1 (ACE Pro) stop_feed_filament stops a feed but not an unwind, so a
stopped pull-back ran its full length while the calibration booked only
the time until the stop; "Return filament to the ACE" then unwound the
same distance again. Unwinds are now stopped with stop_unwind_filament,
and a V1 unwind counts as stopped only when the unit reports ready
within 1.5 s; otherwise the full commanded length is booked once the
move has run out. Pausing a verification pull-back goes the same way.

tests/unit/test_calibration.py drives the calibration against a fake ACE
that obeys or ignores stops.
Pull-backs, forward steps, the verify jogs and the return to the ACE
waited for a busy ACE with wait_ace_ready_on, which blocks the G-code
command for up to a minute and outlasts the web request. After the V1
runaway unwind the page got Moonraker's error JSON and lost track of the
session. These commands now wait at most 2 s and then refuse with a
plain message. Marking the hub at the park position explains what to do
instead of citing the preload reference, the page passes only
Moonraker's message, and a failed or cancelled run can be discarded
from the Calibrate panel.
The hold button needed the mouse held down while looking at the printer,
read like a step size, and its unbounded pull-back is what unloaded the
spool on the ACE Pro. The clear-the-hub step now has a line from the
park position to the toolhead with a handle at the tip and the saved hub
as a mark. Dragging moves nothing; on release one bounded command covers
the difference (never past the park position or the toolhead-sensor
start), under 5 mm nothing is sent. The step buttons carry their
direction. Forward steps accept up to 5000 mm.
Verification fed the raw measured distance and pulled back the raw hub
clearance, without the margins that Save adds. The page now sends the
values Save would write (ACE_CALIBRATION_VERIFY ... LOAD= SWAP=
RETRACT=): the feed budget and the hub clearance include their margins,
the full retract stays exact. For a route without a session result the
page sends the saved values from the config form, so a route can be
verified right after saving, before any restart. Pulling back further at
the hub stop is kept apart from the measured value and adds up over the
runs of a session, and the verify panel lists the values under test.
After saving, the results panel asked for a printer restart (confirmed
twice, and nothing restarted) and verification stayed blocked until one
happened. Now it saves, notes that loads and swaps use the new values
after the next restart, and offers "Calibrate the next slot or ACE" and
"Verify this route" - verification already sends the values from the
page, so it does not depend on the restart.
…es at the sensor

The V1 stop decision waited 1.5 s for 'ready', but the status heartbeat
refreshes once a second and lags 1-2 s, so an ACE Pro that obeyed the
stop could be read as ignoring it (full move booked, filament left in
the tube while the session read parked), and a report taken before the
unit started moving could read as a confirmed stop. It now waits for a
report that arrived at least 2.5 s after the command and judges by when
it says ready: clearly before the move could have run out means obeyed,
around its natural end means ignored.

A forward correction now stops at the toolhead sensor like the
measuring feed; its length comes from the booked pull-back, which can
be off. Verify start and the return to the ACE refuse a busy unit before
switching the active ACE.

The fake ACE in the tests now reports status like ace.py's heartbeat
(stamp on every motor command, fresh report per second with a lag).
The lane encoder counts about 3% more than the commanded distance. Feed
chunks were booked in encoder counts and pull-backs in commanded mm, so
a return over-retracted and verify re-measured the route ~65 mm long.
Encoder readings are now converted with the ratio learned from this
session's completed moves.
The ACE moves about 1.5% more filament per mm pulling back than feeding,
so a check's return pulled the tip ~10 mm behind park and the next check
re-measured. On V2 the return now pulls back the filament the check fed,
converted with the ratio of the check's own hub pull-back. The toolhead
check accepts max(10 mm, 2%) before it re-measures.
Clear blanks load_length, swap_retract_length and retract_length at the
level Save writes (the slot's own values or the ACE's shared ones), through
the same config save. A dialog shows the old values, what each key falls
back to and which slots lose their calibration. An open run of the route
is reset, or cancelled while filament is out so the return to the ACE stays
offered. Undo writes the old values back.

Save no longer reads a second save in a row as failed: it only checks the
log line saveConfigForm writes for this save.
After release the track keeps the target and says how far the tip is
moving and how far it still has to go, with Stop beside it. The step
buttons take the same path. A forward correction now reports the tip
while it moves. The hub mark is drawn only from a hub clearance saved
for this route, not from the global default.
Say up front that the left end of the track is the park position, not
the hub, and describe the stop point by side (the tip has left the hub
on the ACE side) instead of 'past the hub', which reads both ways.
The service runs as root. Started with a strict umask it wrote ace.cfg
as root 0600 and Klipper, running as the printer user, could not open
it. Files written for Klipper (ace.cfg, its backup, slot overrides) are
now readable by all and, when running as root, owned like their
directory.
The drag moves the filament either way, on release. The mark button
records the hub clearance and goes to the park position; nothing is
saved until Results, so it no longer says 'save'. The condition for
marking (the tip out of the hub, in its own Bowden tube to the ACE)
moves to a line above the button.
@physicsG physicsG changed the title WIP: Bowden path calibration - reworked flow, encoder-based distance, new-design theming Bowden path calibration - reworked flow, encoder-based distance, new-design theming Oct 27, 2026
@physicsG

physicsG commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thanks for testing. It was really helpful in marching this feature forward! I pushed an update that addresses all six points, and tested it on my U1 with an ACE 2 Pro. While it may not be perfect, it hopefully serves as a deployable version.

1. ACE 2 measurement: kept. While testing I found that the lane encoder reads about 3% more than the commanded distance, and pull-backs move a little more filament per mm than feeds. The calibration was mixing encoder counts with commanded mm, which made routes come out ~65 mm too long. All bookkeeping is now in commanded mm (the unit the config uses), with encoder readings converted per session.

2. ACE Pro unload runaway:

  • V1 unwinds are stopped with stop_unwind_filament - untested by me
  • An unwind only counts as stopped once a status report arriving at least 2.5 s after the command says ready, clearly before the move could have run out. Otherwise it is booked as the full length, so the tip position is never underestimated.
  • The hold button is gone (see 4).
  • I have no ACE Pro here, so stop_unwind_filament is still untested on real V1 hardware. A test on your ACE Pro would be very welcome.

3. Stuck session and raw error:

  • A failed, cancelled or abandoned run should now reset cleanly, and the next route starts fresh.
  • A busy ACE is refused before anything is switched.
  • Errors show the plain message instead of Moonraker's JSON and traceback.

4. Coarse row / hold button: replaced with your suggestion.

  • A track from the ACE to the toolhead with a marker at the tip. Drag it and the filament moves on release, as one bounded command of the difference.
  • Moves under 5 mm are ignored, and nothing goes past the measured toolhead point.
  • While it moves, the track shows the target, the mm to go and a Stop button.
  • The mm step buttons stay for small corrections.
  • The hub mark is drawn only when the route already has a saved hub clearance.

5. Verify what gets saved:

  • The check now runs with the values Save will write, margins included (load +150, hub clearance +100).
  • On the ACE 2 the return to park is sized by the encoder, so repeated checks don't drift.
  • The toolhead check re-measures only beyond max(10 mm, 2%).

6. Save flow:

  • Save just saves, says the values apply after the next restart, and lets you go on with the next slot or ACE. You restart once at the end.
  • There's also a new Clear option, which removes a route's saved values (per slot or per ACE), with Undo.

Also:

  • Merged current upstream main.
  • Fixed a save issue: with a strict umask the web service wrote ace.cfg as root-only, and Klipper could not open it on restart.
  • Tests are in tests/unit/test_calibration.py.

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged. One open issue on the ACE Pro (V1) side, which I will fix myself since you have no ACE Pro to test on: every verify round there comes out about 100 mm longer (1938, 2036, 2136, 2236 on the same lane). The V1 has no encoder, so the return to park unwinds the commanded length, and on the ACE Pro an unwind moves about 5% more filament than a feed of the same commanded length. Each round therefore starts from a park point further back and stores that as the new load length. On the ACE 2 your encoder return correction prevents this. My fix: without an encoder, the verify only reports the deviation and keeps the calibrated length, and a sensor hit at the very end of the search window counts as not found instead of as a measurement.

Thanks for the calibration work, it runs really well!

@decay71
decay71 merged commit d3d174e into decay71:main Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants